package com.googlecode.bluetools.common.utils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * 
 * @author tanxuqing
 * @date 2009-3-7
 * @since 2.3.0
 */
public class ZipUtils {

	private static final int BUFFER_SIZE = 8096;

	/**
	 * Dump the contents of a zip archive to the dpecified destination.
	 * 
	 * @param input
	 * @param dest
	 * @param overwrite
	 * @throws IOException
	 */
	public static void extract(InputStream input, File dest, boolean overwrite) throws IOException {
		if (!dest.isDirectory()) {
			throw new IOException("Destination must be a directory.");
		}

		if (!dest.exists()) {
			dest.mkdirs();
		}

		ZipInputStream zin = new ZipInputStream(input);
		try {
			byte[] buffer = new byte[1024];

			ZipEntry entry = zin.getNextEntry();
			while (entry != null) {
				String fileName = entry.getName();
				if (fileName.charAt(fileName.length() - 1) == '/') {
					fileName = fileName.substring(0, fileName.length() - 1);
				}
				if (fileName.charAt(0) == '/') {
					fileName = fileName.substring(1);
				}
				if (File.separatorChar != '/') {
					fileName = fileName.replace('/', File.separatorChar);
				}
				File file = new File(dest, fileName);
				if (entry.isDirectory()) {
					// make sure the directory exists
					file.mkdirs();
					zin.closeEntry();
				}
				else {
					if (file.exists() && overwrite == false)
						continue;

					// make sure the directory exists
					File parent = file.getParentFile();
					if (parent != null && !parent.exists()) {
						parent.mkdirs();
					}

					// dump the file
					OutputStream out = new FileOutputStream(file);
					int len = 0;
					while ((len = zin.read(buffer, 0, buffer.length)) != -1) {
						out.write(buffer, 0, len);
					}
					out.flush();
					out.close();
					zin.closeEntry();
					file.setLastModified(entry.getTime());
				}
				entry = zin.getNextEntry();
			}
		}
		finally {
			if (zin != null) {
				zin.close();
			}
		}
	}

	/**
	 * Dump the contents of a zip archive to the dpecified destination.
	 * 
	 * @param zipFile
	 * @param dest
	 * @param overwrite
	 * @throws IOException
	 */
	public static void extract(File zipFile, File dest, boolean overwrite) throws IOException {
		extract(new FileInputStream(zipFile), dest, overwrite);
	}

	public static void zipFile(File srcFile, String destZipFile) throws Exception {
		zipFile(srcFile, new File(destZipFile));
	}

	public static void zipFile(File srcFile, File destZipFile) throws Exception {
		FileOutputStream fileWriter = new FileOutputStream(destZipFile);
		ZipOutputStream zos = new ZipOutputStream(fileWriter);

		if (srcFile.isDirectory()) {
			for (File file : srcFile.listFiles()) {
				if (file.isDirectory()) {
					addFolderToZip("", file, zos);
				}
				else {
					addFileToZip("", file, zos);
				}
			}
		}
		else {
			addFileToZip("", srcFile, zos);
		}

		zos.flush();
		zos.close();
	}

	private static void addFolderToZip(String path, File srcFolder, ZipOutputStream zip) throws Exception {
		for (File file : srcFolder.listFiles()) {
			String entryPath = null;
			if (path.equals("")) {
				entryPath = srcFolder.getName();
			}
			else {
				entryPath = path + "/" + srcFolder.getName();
			}
			addFileToZip(entryPath, file, zip);
		}
	}

	private static void addFileToZip(String path, File srcFile, ZipOutputStream zip) throws Exception {
		if (srcFile.isDirectory()) {
			addFolderToZip(path, srcFile, zip);
		}
		else {
			byte[] buf = new byte[BUFFER_SIZE];
			int len;
			FileInputStream fin = new FileInputStream(srcFile);
			try {
				String entryPath = null;
				if (path.length() == 0) {
					entryPath = srcFile.getName();
				}
				else {
					entryPath = path + "/" + srcFile.getName();
				}
				zip.putNextEntry(new ZipEntry(entryPath));
				while ((len = fin.read(buf)) > 0) {
					zip.write(buf, 0, len);
				}
			}
			finally {
				fin.close();
			}
		}
	}

	/**
	 * Returns a Map with each entry data (byte[]) by the entry name.
	 * 
	 * <p>
	 * The entry name is the entry full path inside the zip file.
	 * 
	 * @param zipFileName
	 * @return Map
	 */
	public static Map getZipEntryDataByZipEntryName(String zipFileName) {
		Map entrySizeByEntryName = new Hashtable();
		Map entryDataByEntryName = new Hashtable();

		try {
			ZipFile zipFile = null;
			try {
				zipFile = new ZipFile(zipFileName);
				for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
					ZipEntry entry = (ZipEntry) e.nextElement();
					entrySizeByEntryName.put(entry.getName(), Integer.valueOf((int) entry.getSize()));
				}
			}
			finally {
				if (zipFile != null)
					zipFile.close();
			}

			ZipInputStream zipInputStream = null;
			try {
				zipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFileName)));
				for (ZipEntry entry = zipInputStream.getNextEntry(); entry != null; entry = zipInputStream
						.getNextEntry()) {
					if (entry.isDirectory())
						continue;

					int size = (int) entry.getSize();
					if (size == -1)
						size = ((Integer) entrySizeByEntryName.get(entry.getName())).intValue();

					byte[] data = new byte[size];
					int offset = 0;
					int ret = 0;
					while ((size - offset) > 0) {
						ret = zipInputStream.read(data, offset, size - offset);
						if (ret == -1)
							break;

						offset += ret;
					}

					entryDataByEntryName.put(entry.getName(), data);
				}
			}
			finally {
				if (zipInputStream != null)
					zipInputStream.close();
			}
		}
		catch (Exception e) {
		}

		return entryDataByEntryName;
	}
}
